package pl.mchaniewski.ryneczek.web.user;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.faces.application.FacesMessage;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotNull;

import org.primefaces.model.chart.CartesianChartModel;
import org.primefaces.model.chart.LineChartSeries;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;

import pl.mchaniewski.ryneczek.pojo.Company;
import pl.mchaniewski.ryneczek.pojo.Index;
import pl.mchaniewski.ryneczek.pojo.Order;
import pl.mchaniewski.ryneczek.pojo.Portfolio;
import pl.mchaniewski.ryneczek.pojo.Quotation;
import pl.mchaniewski.ryneczek.services.CompanyService;
import pl.mchaniewski.ryneczek.services.IndexService;
import pl.mchaniewski.ryneczek.services.OrderService;
import pl.mchaniewski.ryneczek.services.PortfolioService;
import pl.mchaniewski.ryneczek.services.QuotationService;
import pl.mchaniewski.ryneczek.utilities.DateUtil;
import pl.mchaniewski.ryneczek.utilities.FacesMessageUtil;
import pl.mchaniewski.ryneczek.utilities.I18nUtil;
import pl.mchaniewski.ryneczek.utilities.NavigationUtil;
import pl.mchaniewski.ryneczek.web.FormButton;

@Component
@Scope("request")
@PreAuthorize("isAuthenticated()")
public class CompanyDetailsBean extends FormButton {
    private static final Logger logger = LoggerFactory
            .getLogger(CompanyDetailsBean.class);
    private CompanyService companyService;
    private QuotationService quotationService;
    private PortfolioService portfolioService;
    private OrderService orderService;
    private IndexService indexService;
    private SelectedPortfolioBean selectedPortfolioBean;
    private PaginationBean paginationBean;
    private Company company;
    private BigDecimal shareValue;
    private CartesianChartModel dailyChange;
    private CartesianChartModel tradingVolume;
    private CartesianChartModel marketValue;
    private List<Quotation> todays;
    private boolean lastPage;
    private boolean firstPage;
    private static final int QUOTATIONS_PER_PAGE = 65;

    /* Form */
    private Map<String, Object> transactionTypes;
    @NotNull(message = "{companydetailsbean.transactionNotNull}")
    private String transactionType;
    @DecimalMin(value = "1", message = "{companydetailsbean.amountMin}")
    @NotNull(message = "{companydetailsbean.amountNotNull}")
    private Integer amount;
    @NotNull(message = "{companydetailsbean.expiryDateNotNull}")
    private Date expiryDate;
    private boolean typeOne;
    private boolean typeTwo;
    private boolean typeThree;
    private BigDecimal priceOne;
    private BigDecimal priceThree;
    private String canSellAmount;
    private String canBuyAmount;
    private String sharePrice;
    private String commissionPercentage;
    private String commissionValue;

    /* PostConstruct parameters */
    private List<Order> activeOrders;

    /* Request parameters */
    private String name;

    @Autowired
    public void setCompanyService(CompanyService companyService) {
        this.companyService = companyService;
    }

    @Autowired
    public void setQuotationService(QuotationService quotationService) {
        this.quotationService = quotationService;
    }

    @Autowired
    public void setPortfolioService(PortfolioService portfolioService) {
        this.portfolioService = portfolioService;
    }

    @Autowired
    public void setOrderService(OrderService orderService) {
        this.orderService = orderService;
    }

    @Autowired
    public void setSelectedPortfolioBean(
            SelectedPortfolioBean selectedPortfolioBean) {
        this.selectedPortfolioBean = selectedPortfolioBean;
    }

    @Autowired
    public void setIndexService(IndexService indexService) {
        this.indexService = indexService;
    }

    @Autowired
    public void setPaginationBean(PaginationBean paginationBean) {
        this.paginationBean = paginationBean;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public CartesianChartModel getDailyChange() {
        return dailyChange;
    }

    public CartesianChartModel getTradingVolume() {
        return tradingVolume;
    }

    public CartesianChartModel getMarketValue() {
        return marketValue;
    }

    public String getTransactionType() {
        return transactionType;
    }

    public void setTransactionType(String transactionType) {
        this.transactionType = transactionType;
    }

    public Integer getAmount() {
        return amount;
    }

    public void setAmount(Integer amount) {
        this.amount = amount;
    }

    public Date getExpiryDate() {
        return expiryDate;
    }

    public void setExpiryDate(Date expiryDate) {
        this.expiryDate = expiryDate;
    }

    public boolean isTypeOne() {
        return typeOne;
    }

    public void setTypeOne(boolean typeOne) {
        this.typeOne = typeOne;
    }

    public boolean isTypeTwo() {
        return typeTwo;
    }

    public void setTypeTwo(boolean typeTwo) {
        this.typeTwo = typeTwo;
    }

    public boolean isTypeThree() {
        return typeThree;
    }

    public void setTypeThree(boolean typeThree) {
        this.typeThree = typeThree;
    }

    public BigDecimal getPriceOne() {
        return priceOne;
    }

    public void setPriceOne(BigDecimal priceOne) {
        this.priceOne = priceOne;
    }

    public BigDecimal getPriceThree() {
        return priceThree;
    }

    public void setPriceThree(BigDecimal priceThree) {
        this.priceThree = priceThree;
    }

    public Map<String, Object> getTransactionTypes() {
        return transactionTypes;
    }

    public String getCanSellAmount() {
        return canSellAmount;
    }

    public String getCanBuyAmount() {
        return canBuyAmount;
    }

    public String getSharePrice() {
        return sharePrice;
    }

    public String getCommissionPercentage() {
        return commissionPercentage;
    }

    public String getCommissionValue() {
        return commissionValue;
    }

    public BigDecimal getShareValue() {
        return shareValue;
    }

    public List<Order> getActiveOrders() {
        return activeOrders;
    }

    public Company getCompany() {
        if (company != null) {
            if (company.getName().equals(name)) {
                return company;
            }
        }

        if (name != null) {
            company = companyService.findByName(name);
            return company;
        }

        return null;
    }

    public Integer getPage() {
        return paginationBean.getPage(company.getCompanyid());
    }

    public void setPage(Integer page) {
        paginationBean.setPage(company.getCompanyid(), page);
    }

    public boolean isLastPage() {
        return lastPage;
    }

    public boolean isFirstPage() {
        return firstPage;
    }

    @PostConstruct
    public void preInit() {
        if (logger.isDebugEnabled()) {
            logger.debug("preInit() (PostConstruct) IS CALLED");
        }

        if (selectedPortfolioBean.getSelectedPortfolioId() == null) {
            NavigationUtil.directRedirect(NavigationUtil.DIRECT_QUOTATION_LIST);
            return;
        }
    }

    public String initBasedOnPathParam() {
        if (logger.isDebugEnabled()) {
            logger.debug("initBasedOnPathParam() (URLAction) IS CALLED");
        }

        if (name == null || name.equals("")) {
            NavigationUtil.directRedirect(NavigationUtil.DIRECT_QUOTATION_LIST);
            return null;
        }

        if (getCompany() == null) {
            NavigationUtil.directRedirect(NavigationUtil.DIRECT_QUOTATION_LIST);
            return null;
        }

        if (!getCompany().isWasInLastUpdate()) {
            NavigationUtil.directRedirect(NavigationUtil.DIRECT_QUOTATION_LIST);
            return null;
        }

        if (indexService.isCompanyInIndex(Index.OUT_OF_SYSTEM_INDICES,
                getCompany())) {
            NavigationUtil.directRedirect(NavigationUtil.DIRECT_QUOTATION_LIST);
            return null;
        }

        initLinearModels();
        initSelectOneMenu();
        initFields();

        updateOrderList();

        return null;
    }

    private void updateOrderList() {
        activeOrders = orderService.findByPortfolioIdCompanyIdAndStatus(
                selectedPortfolioBean.getSelectedPortfolioId(), getCompany()
                        .getCompanyid(), Order.STATUS_ACCEPTED);
    }

    private void initSelectOneMenu() {
        transactionTypes = new LinkedHashMap<String, Object>();
        transactionType = Order.TRANSACTION_TYPE_BUY;

        transactionTypes.put(
                I18nUtil.getLocalizedMessage("companydetailsbean.buy"),
                Order.TRANSACTION_TYPE_BUY);

        Integer sharesNumberInPortfolio = portfolioService
                .getPortfolioCompanySharesAmount(
                        selectedPortfolioBean.getSelectedPortfolioId(),
                        getCompany().getCompanyid());

        if (sharesNumberInPortfolio != null && sharesNumberInPortfolio > 0) {
            transactionTypes.put(
                    I18nUtil.getLocalizedMessage("companydetailsbean.sell"),
                    Order.TRANSACTION_TYPE_SELL);

            canSellAmount = sharesNumberInPortfolio.toString();
        }
    }

    private void initFields() {
        BigDecimal balance = getPortfolioBalance();
        Quotation lastQuotations = getLastQuotation();

        if (lastQuotations != null) {
            BigDecimal currVal = lastQuotations.getCurrentValue();
            int amount = balance.divide(currVal, 2, RoundingMode.DOWN)
                    .intValue();
            BigDecimal total = getTotal(amount, currVal);

            while (total.compareTo(balance) == 1) {
                --amount;
                total = getTotal(amount, currVal);
            }

            canBuyAmount = (amount > 0) ? Integer.toString(amount) : "0";
            sharePrice = lastQuotations.getCurrentValue().toString();
            shareValue = lastQuotations.getCurrentValue();
            commissionPercentage = Order.PERCENTAGE_COMMISSION;
            commissionValue = Order.MINIMUM_COMMISSION;
        }

        expiryDate = DateUtil.addDate(DateUtil.getNow(), 1, 0, 0);
    }

    public void initLinearModels() {
        if (todays == null) {
            Quotation latest = quotationService.findLatest(getCompany()
                    .getCompanyid(), true);
            if (latest != null) {
                todays = quotationService.findByDateAndCompanyId(getCompany()
                        .getCompanyid(), latest.getListingDate());
            }
        }

        if (todays == null || (todays != null && todays.size() == 0)) {
            dailyChange = null;
            tradingVolume = null;
            marketValue = null;

            return;
        }

        dailyChange = new CartesianChartModel();
        tradingVolume = new CartesianChartModel();
        marketValue = new CartesianChartModel();

        setPage((getPage() == null) ? calculateMaxNumberOfPages() : getPage());

        loadPage(getPage());
    }

    private void loadPage(int pageNumber) {
        if (logger.isDebugEnabled()) {
            logger.debug("loadPage( [PAGE: {}] )", pageNumber);
        }

        dailyChange.clear();
        tradingVolume.clear();
        marketValue.clear();

        LineChartSeries currrentSeries = new LineChartSeries();
        currrentSeries.setLabel(I18nUtil
                .getLocalizedMessage("companydetailsbean.chartCurrent"));

        LineChartSeries marketValueSeries = new LineChartSeries();
        marketValueSeries.setLabel(I18nUtil
                .getLocalizedMessage("companydetailsbean.chartMarketValue"));

        LineChartSeries tradingVolumeSeries = new LineChartSeries();
        tradingVolumeSeries.setLabel(I18nUtil
                .getLocalizedMessage("companydetailsbean.chartVolume"));

        if (todays.size() <= QUOTATIONS_PER_PAGE) {
            for (Quotation q : todays) {
                currrentSeries.set(q.getFormattedTime(), q.getCurrentValue());
                marketValueSeries.set(q.getFormattedTime(), q.getMarketValue());
                tradingVolumeSeries.set(q.getFormattedTime(),
                        q.getTradingVolume());
            }

            firstPage = true;
            lastPage = true;
            setPage(1);
        } else {
            int maxPages = calculateMaxNumberOfPages();

            if (maxPages < 1) {
                dailyChange.addSeries(currrentSeries);
                tradingVolume.addSeries(marketValueSeries);
                marketValue.addSeries(tradingVolumeSeries);
            }

            List<Quotation> subList = null;
            if (pageNumber < 1 || maxPages == 1) {
                pageNumber = 1;

                subList = todays.subList(0, todays.size() - 1);
            } else {
                if (pageNumber > maxPages || pageNumber == maxPages) {
                    pageNumber = maxPages;

                    subList = todays.subList((todays.size() - 1)
                            - QUOTATIONS_PER_PAGE, todays.size() - 1);
                } else {
                    int to = (pageNumber * QUOTATIONS_PER_PAGE) - 1;
                    int from = to - 64;

                    subList = todays.subList(from, to);
                }
            }
            firstPage = (pageNumber == 1) ? true : false;
            if (maxPages == 1) {
                lastPage = true;
            } else {
                lastPage = (pageNumber == maxPages) ? true : false;
            }
            setPage(pageNumber);

            for (Quotation q : subList) {
                currrentSeries.set(q.getFormattedTime(), q.getCurrentValue());
                marketValueSeries.set(q.getFormattedTime(), q.getMarketValue());
                tradingVolumeSeries.set(q.getFormattedTime(),
                        q.getTradingVolume());
            }
        }

        dailyChange.addSeries(currrentSeries);
        tradingVolume.addSeries(marketValueSeries);
        marketValue.addSeries(tradingVolumeSeries);
    }

    private int calculateMaxNumberOfPages() {
        int max = todays.size() / QUOTATIONS_PER_PAGE;
        if (todays.size() - max * QUOTATIONS_PER_PAGE > 0) {
            ++max;
        }

        return max;
    }

    private BigDecimal getPortfolioBalance() {
        Integer portfolioId = selectedPortfolioBean.getSelectedPortfolioId();
        BigDecimal realBalance = portfolioService
                .getPortfolioRealBalance(portfolioId);

        return realBalance;
    }

    private Quotation getLastQuotation() {
        Quotation latest = quotationService.findLatest(getCompany()
                .getCompanyid(), true);

        return (latest != null) ? latest : null;
    }

    private BigDecimal getTotal(Integer amount, BigDecimal currentValue) {
        BigDecimal transactionVal = Order.getTransactionValue(amount,
                currentValue);
        BigDecimal commissionVal = Order.getCommissionValue(transactionVal);
        return transactionVal.add(commissionVal);
    }

    public String saveOrder() {
        if (!typeOne && !typeTwo && !typeThree) {
            FacesMessageUtil.addMessage(getButton(), I18nUtil
                    .getLocalizedMessage("companydetailsbean.orderType"),
                    FacesMessage.SEVERITY_ERROR);

            return NavigationUtil.SAME_PAGE;
        }

        if (typeOne && typeTwo || typeOne && typeThree || typeTwo && typeThree) {
            FacesMessageUtil
                    .addMessage(
                            getButton(),
                            I18nUtil.getLocalizedMessage("companydetailsbean.onlyOneOrderType"),
                            FacesMessage.SEVERITY_ERROR);

            return NavigationUtil.SAME_PAGE;
        } else {
            if (typeOne
                    && (priceOne == null || (priceOne
                            .compareTo(BigDecimal.ZERO) == -1 || priceOne
                            .compareTo(BigDecimal.ZERO) == 0))) {
                FacesMessageUtil.addMessage(getButton(), I18nUtil
                        .getLocalizedMessage("companydetailsbean.priceOne"),
                        FacesMessage.SEVERITY_ERROR);

                return NavigationUtil.SAME_PAGE;
            }

            if (typeThree
                    && (priceThree == null || (priceThree
                            .compareTo(BigDecimal.ZERO) == -1 || priceThree
                            .compareTo(BigDecimal.ZERO) == 0))) {
                FacesMessageUtil.addMessage(getButton(), I18nUtil
                        .getLocalizedMessage("companydetailsbean.priceThree"),
                        FacesMessage.SEVERITY_ERROR);

                return NavigationUtil.SAME_PAGE;
            }
        }

        if (getAmount() == null || getAmount() <= 0) {
            FacesMessageUtil.addMessage(getButton(), I18nUtil
                    .getLocalizedMessage("companydetailsbean.amountMin"),
                    FacesMessage.SEVERITY_ERROR);

            return NavigationUtil.SAME_PAGE;
        }

        if (DateUtil.isDateBeforeNow(getExpiryDate())) {
            FacesMessageUtil.addMessage(getButton(), I18nUtil
                    .getLocalizedMessage("companydetailsbean.expiryDate"),
                    FacesMessage.SEVERITY_ERROR);

            return NavigationUtil.SAME_PAGE;
        }

        if (!transactionType.equals(Order.TRANSACTION_TYPE_BUY)
                && !transactionType.equals(Order.TRANSACTION_TYPE_SELL)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Invalid transaction type: {}",
                        getTransactionType());
            }

            FacesMessageUtil
                    .addMessage(
                            getButton(),
                            I18nUtil.getLocalizedMessage("companydetailsbean.invalidTransactionType"),
                            FacesMessage.SEVERITY_ERROR);

            return NavigationUtil.SAME_PAGE;
        }

        Portfolio portfolio = portfolioService.findById(selectedPortfolioBean
                .getSelectedPortfolioId());
        Company company = getCompany();

        List<Order> activeOrders = orderService
                .findByPortfolioIdCompanyIdAndStatus(
                        portfolio.getPortfolioid(), company.getCompanyid(),
                        Order.STATUS_ACCEPTED);
        if (activeOrders != null && activeOrders.size() > 0) {
            FacesMessageUtil.addMessage(getButton(), I18nUtil
                    .getLocalizedMessage("companydetailsbean.oneActiveOrder"),
                    FacesMessage.SEVERITY_ERROR);

            return NavigationUtil.SAME_PAGE;
        }

        BigDecimal balance = getPortfolioBalance();
        Quotation lastQuotation = getLastQuotation();
        if (lastQuotation == null) {
            NavigationUtil.directRedirect(NavigationUtil.DIRECT_QUOTATION_LIST);
            return null;
        }

        if (transactionType.equals(Order.TRANSACTION_TYPE_BUY)) {
            BigDecimal totalVal = getTotal(getAmount(),
                    lastQuotation.getCurrentValue());

            if (balance.compareTo(totalVal) == -1) {
                FacesMessageUtil
                        .addMessage(
                                getButton(),
                                I18nUtil.getLocalizedMessage("companydetailsbean.notEnaughFunds"),
                                FacesMessage.SEVERITY_ERROR);

                return NavigationUtil.SAME_PAGE;
            }
        }

        if (transactionType.equals(Order.TRANSACTION_TYPE_SELL)) {
            Integer currentSharesAmount = portfolioService
                    .getPortfolioCompanySharesAmount(
                            portfolio.getPortfolioid(), company.getCompanyid());

            if (currentSharesAmount == null) {
                FacesMessageUtil
                        .addMessage(
                                getButton(),
                                I18nUtil.getLocalizedMessage("companydetailsbean.problemWithSharesAmount"),
                                FacesMessage.SEVERITY_FATAL);

                return NavigationUtil.SAME_PAGE;
            }

            if (currentSharesAmount < getAmount()) {
                FacesMessageUtil
                        .addMessage(
                                getButton(),
                                I18nUtil.getLocalizedMessage("companydetailsbean.notEnaughStocks"),
                                FacesMessage.SEVERITY_ERROR);

                return NavigationUtil.SAME_PAGE;
            }
        }

        String transaction = getTransactionType();
        BigDecimal originalPrice = getLastQuotation().getCurrentValue();
        int securitiesAmount = getAmount();
        BigDecimal setPrice = null;
        int priceType = Order.PRICE_TYPE_PKC;
        Date expiryDate = getExpiryDate();

        if (typeOne) {
            setPrice = priceOne;
            priceType = Order.PRICE_TYPE_PRICE_LIMIT;
        }

        if (typeTwo) {
            priceType = Order.PRICE_TYPE_PKC;
        }

        if (typeThree) {
            setPrice = priceThree;
            priceType = Order.PRICE_TYPE_STOP_LOSS;
        }

        Order newOrder = orderService.createNewOrder(portfolio, company,
                transaction, originalPrice, securitiesAmount, setPrice,
                priceType, expiryDate);
        if (newOrder != null) {
            FacesMessageUtil.addMessage(getButton(),
                    I18nUtil.getLocalizedMessage("companydetailsbean.success"),
                    FacesMessage.SEVERITY_INFO);

            initBasedOnPathParam();
        } else {
            FacesMessageUtil.addMessage(getButton(), I18nUtil
                    .getLocalizedMessage("companydetailsbean.notSuccess"),
                    FacesMessage.SEVERITY_WARN);
        }

        NavigationUtil.directRedirect(NavigationUtil.DIRECT_COMPANY
                + getCompany().getName());
        return NavigationUtil.SAME_PAGE;
    }

    public String cancelOrder(Order cancelOrder) {
        if (logger.isDebugEnabled()) {
            logger.debug("cancelOrder() is called. Order: {}", cancelOrder);
        }

        if (cancelOrder == null) {
            if (logger.isDebugEnabled()) {
                logger.debug("Order not selected.");
            }

            activeOrders.clear();
            NavigationUtil.directRedirect(NavigationUtil.DIRECT_COMPANY
                    + getCompany().getName());
            return NavigationUtil.SAME_PAGE;
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Order id: {}", cancelOrder.getOrderid());
            for (Order o : activeOrders) {
                logger.debug("Order on activeOrders: {}", o.getOrderid());
            }
        }

        if (activeOrders == null) {
            NavigationUtil.directRedirect(NavigationUtil.DIRECT_COMPANY
                    + getCompany().getName());
            return NavigationUtil.SAME_PAGE;
        }

        boolean contains = false;
        for (Order o : activeOrders) {
            if (o.getOrderid().equals(cancelOrder.getOrderid())) {
                contains = true;
                break;
            }
        }
        if (!contains) {
            if (logger.isDebugEnabled()) {
                logger.debug("Order list doesn't contain order with ID {}.",
                        cancelOrder.getOrderid());
            }

            NavigationUtil.directRedirect(NavigationUtil.DIRECT_COMPANY
                    + getCompany().getName());
            return NavigationUtil.SAME_PAGE;
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Selected order id {} and comapny name {}",
                    cancelOrder.getOrderid(), cancelOrder.getCompany()
                            .getName());
        }

        if (!cancelOrder.getStatus().equals(Order.STATUS_ACCEPTED)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Can't cancel order with status other than {}.",
                        Order.STATUS_ACCEPTED);
            }

            activeOrders.clear();
            NavigationUtil.directRedirect(NavigationUtil.DIRECT_COMPANY
                    + getCompany().getName());
            return NavigationUtil.SAME_PAGE;
        }

        cancelOrder.setStatus(Order.STATUS_CANCELED);
        if (logger.isDebugEnabled()) {
            logger.debug("Order status changed to {}", Order.STATUS_CANCELED);
        }

        if (orderService.save(cancelOrder) != null) {
            if (logger.isDebugEnabled()) {
                logger.debug("Order saved successfully. Comapny name: {}",
                        cancelOrder.getCompany().getName());
            }

            updateOrderList();
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("Couldn't save order. Service returned null.");
            }
        }

        NavigationUtil.directRedirect(NavigationUtil.DIRECT_COMPANY
                + getCompany().getName());
        return NavigationUtil.SAME_PAGE;
    }

    public void nextPage() {
        loadPage(getPage() + 1);
    }

    public void previousPage() {
        loadPage(getPage() - 1);
    }

    public void firstPage() {
        loadPage(1);
    }

    public void lastPage() {
        loadPage(calculateMaxNumberOfPages());
    }
}
